// This is an open source non-commercial project. Dear PVS-Studio, please check it.
// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com

// ReSharper disable CheckNamespace
// ReSharper disable ClassNeverInstantiated.Global
// ReSharper disable CommentTypo
// ReSharper disable IdentifierTypo
// ReSharper disable InconsistentNaming
// ReSharper disable StringLiteralTypo
// ReSharper disable UnusedParameter.Local

/*
 * Ars Magna project, http://arsmagna.ru
 */

#region Using directives

using System;
using System.Collections.Generic;
using System.ComponentModel;

using AM.Reporting.Table;
using AM.Reporting.Data;
using AM.Reporting.Utils;

using System.Drawing.Design;

#endregion

#nullable enable

namespace AM.Reporting.Matrix
{
    /// <summary>
    /// Describes how the even style is applied to a matrix.
    /// </summary>
    public enum MatrixEvenStylePriority
    {
        /// <summary>
        /// The even style is applied to matrix rows.
        /// </summary>
        Rows,

        /// <summary>
        /// The even style is applied to matrix columns.
        /// </summary>
        Columns
    }


    /// <summary>
    /// Represents the matrix object that is used to print pivot table (also known as cross-tab).
    /// </summary>
    /// <remarks>
    /// The matrix consists of the following elements: columns, rows and data cells. Each element is
    /// represented by the <b>descriptor</b>. The <see cref="MatrixHeaderDescriptor"/> class is used
    /// for columns and rows; the <see cref="MatrixCellDescriptor"/> is used for data cells.
    /// The <see cref="Data"/> property holds three collections of descriptors - <b>Columns</b>,
    /// <b>Rows</b> and <b>Cells</b>.
    /// <para/>To create the matrix in a code, you should perform the following actions:
    /// <list type="bullet">
    ///   <item>
    ///     <description>create an instance of the <b>MatrixObject</b> and add it to the report;</description>
    ///   </item>
    ///   <item>
    ///     <description>create descriptors for columns, rows and cells and add it to the
    ///     collections inside the <see cref="Data"/> property;</description>
    ///   </item>
    ///   <item>
    ///     <description>call the <see cref="BuildTemplate"/> method to create the matrix template
    ///     that will be used to create a result;</description>
    ///   </item>
    ///   <item>
    ///     <description>modify the matrix template (change captions, set the visual appearance).</description>
    ///   </item>
    /// </list>
    /// <para/>To connect the matrix to a datasource, use the <see cref="DataSource"/> property. If
    /// this property is not set, the result matrix will be empty. In this case you may use
    /// the <see cref="ManualBuild"/> event handler to fill the matrix.
    /// </remarks>
    /// <example>This example demonstrates how to create a matrix in a code.
    /// <code>
    /// // create an instance of MatrixObject
    /// MatrixObject matrix = new MatrixObject();
    /// matrix.Name = "Matrix1";
    /// // add it to the report title band of the first report page
    /// matrix.Parent = (report.Pages[0] as ReportPage).ReportTitle;
    ///
    /// // create two column descriptors
    /// MatrixHeaderDescriptor column = new MatrixHeaderDescriptor("[MatrixDemo.Year]");
    /// matrix.Data.Columns.Add(column);
    /// column = new MatrixHeaderDescriptor("[MatrixDemo.Month]");
    /// matrix.Data.Columns.Add(column);
    ///
    /// // create one row descriptor
    /// MatrixHeaderDescriptor row = new MatrixHeaderDescriptor("[MatrixDemo.Name]");
    /// matrix.Data.Rows.Add(row);
    ///
    /// // create one data cell
    /// MatrixCellDescriptor cell = new MatrixCellDescriptor("[MatrixDemo.Revenue]", MatrixAggregateFunction.Sum);
    /// matrix.Data.Cells.Add(cell);
    ///
    /// // connect matrix to a datasource
    /// matrix.DataSource = Report.GetDataSource("MatrixDemo");
    ///
    /// // create the matrix template
    /// matrix.BuildTemplate();
    ///
    /// // change the style
    /// matrix.Style = "Green";
    ///
    /// // change the column and row total's text to "Grand Total"
    /// matrix.Data.Columns[0].TemplateTotalCell.Text = "Grand Total";
    /// matrix.Data.Rows[0].TemplateTotalCell.Text = "Grand Total";
    /// </code>
    /// </example>
    public partial class MatrixObject : TableBase
    {
        #region Fields

        private bool autoSize;
        private bool cellsSideBySide;
        private DataSourceBase dataSource;
        private bool showTitle;
        private string style;
        private bool saveVisible;

        #endregion

        #region Properties

        /// <summary>
        /// Allows to fill the matrix in code.
        /// </summary>
        /// <remarks>
        /// In most cases the matrix is connected to a datasource via the <see cref="DataSource"/>
        /// property. When you run a report, the matrix is filled with datasource values automatically.
        /// <para/>Using this event, you can put additional values to the matrix or even completely fill it
        /// with own values (if <see cref="DataSource"/> is set to <b>null</b>. To do this, call the
        /// <b>Data.AddValue</b> method. See the <see cref="MatrixData.AddValue(object[],object[],object[])"/>
        /// method for more details.
        /// </remarks>
        /// <example>This example shows how to fill a matrix with own values.
        /// <code>
        /// // suppose we have a matrix with one column, row and data cell.
        /// // provide 3 one-dimensional arrays with one element in each to the AddValue method
        /// Matrix1.Data.AddValue(
        ///   new object[] { 1996 },
        ///   new object[] { "Andrew Fuller" },
        ///   new object[] { 123.45f });
        /// Matrix1.Data.AddValue(
        ///   new object[] { 1997 },
        ///   new object[] { "Andrew Fuller" },
        ///   new object[] { 21.35f });
        /// Matrix1.Data.AddValue(
        ///   new object[] { 1997 },
        ///   new object[] { "Nancy Davolio" },
        ///   new object[] { 421.5f });
        ///
        /// // this code will produce the following matrix:
        /// //               |  1996  |  1997  |
        /// // --------------+--------+--------+
        /// // Andrew Fuller |  123.45|   21.35|
        /// // --------------+--------+--------+
        /// // Nancy Davolio |        |  421.50|
        /// // --------------+--------+--------+
        /// </code>
        /// </example>
        public event EventHandler ManualBuild;

        /// <summary>
        /// Allows to modify the prepared matrix elements such as cells, rows, columns.
        /// </summary>
        public event EventHandler ModifyResult;

        /// <summary>
        /// Allows to modify the prepared matrix elements such as cells, rows, columns.
        /// </summary>
        public event EventHandler AfterTotals;

        /// <summary>
        /// Gets or sets a value that determines whether the matrix must calculate column/row sizes automatically.
        /// </summary>
        [DefaultValue (true)]
        [Category ("Behavior")]
        public bool AutoSize
        {
            get => autoSize;
            set
            {
                autoSize = value;
                foreach (TableColumn column in Columns)
                {
                    column.AutoSize = AutoSize;
                }

                foreach (TableRow row in Rows)
                {
                    row.AutoSize = AutoSize;
                }
            }
        }

        /// <summary>
        /// Gets or sets a value that determines how to print multiple data cells.
        /// </summary>
        /// <remarks>
        /// This property can be used if matrix has two or more data cells. Default property value
        /// is <b>false</b> - that means the data cells will be stacked.
        /// </remarks>
        [DefaultValue (false)]
        [Category ("Behavior")]
        public bool CellsSideBySide
        {
            get => cellsSideBySide;
            set
            {
                if (cellsSideBySide != value)
                {
                    cellsSideBySide = value;
                    if (IsDesigning)
                    {
                        foreach (MatrixCellDescriptor descr in Data.Cells)
                        {
                            descr.TemplateColumn = null;
                            descr.TemplateRow = null;
                        }

                        BuildTemplate();
                    }
                }
            }
        }

        /// <summary>
        /// Gets or sets a value indicating that the side-by-side cells must be kept together on the same page.
        /// </summary>
        [DefaultValue (false)]
        [Category ("Behavior")]
        public bool KeepCellsSideBySide { get; set; }

        /// <summary>
        /// Gets or sets a data source.
        /// </summary>
        /// <remarks>
        /// When you create the matrix in the designer by drag-drop data columns into it,
        /// this property will be set automatically. However you need to set it if you create
        /// the matrix in code.
        /// </remarks>
        [Category ("Data")]
        public DataSourceBase DataSource
        {
            get => dataSource;
            set
            {
                if (dataSource != value)
                {
                    if (dataSource != null)
                    {
                        dataSource.Disposed -= new EventHandler (DataSource_Disposed);
                    }

                    if (value != null)
                    {
                        value.Disposed += new EventHandler (DataSource_Disposed);
                    }
                }

                dataSource = value;
            }
        }

        /// <summary>
        /// Gets the row filter expression.
        /// </summary>
        /// <remarks>
        /// This property can contain any valid boolean expression. If the expression returns <b>false</b>,
        /// the corresponding data row will be skipped.
        /// </remarks>
        [Category ("Data")]
        [Editor ("AM.Reporting.TypeEditors.ExpressionEditor, AM.Reporting", typeof (UITypeEditor))]
        public string Filter { get; set; }

        /// <summary>
        /// Gets or sets a value indicating whether to show a title row.
        /// </summary>
        [DefaultValue (false)]
        [Category ("Behavior")]
        public bool ShowTitle
        {
            get => showTitle;
            set
            {
                showTitle = value;
                if (IsDesigning)
                {
                    BuildTemplate();
                }
            }
        }

        /// <summary>
        /// Gets or sets a matrix style.
        /// </summary>
        [Category ("Appearance")]
        [Editor ("AM.Reporting.TypeEditors.MatrixStyleEditor, AM.Reporting", typeof (UITypeEditor))]
        public new string Style
        {
            get => style;
            set
            {
                style = value;
                Helper.UpdateStyle();
            }
        }

        /// <summary>
        /// Gets or sets even style priority for matrix cells.
        /// </summary>
        [Category ("Behavior")]
        [DefaultValue (MatrixEvenStylePriority.Rows)]
        public MatrixEvenStylePriority MatrixEvenStylePriority { get; set; }

        /// <summary>
        /// Gets or sets need split rows.
        /// </summary>
        [Category ("Behavior")]
        [DefaultValue (false)]
        public bool SplitRows { get; set; }

        /// <summary>
        /// Gets or sets a value indicating that empty matrix should be printed.
        /// </summary>
        [Category ("Behavior")]
        [DefaultValue (true)]
        public bool PrintIfEmpty { get; set; }

        /// <summary>
        /// Gets or sets a script method name that will be used to handle the
        /// <see cref="ManualBuild"/> event.
        /// </summary>
        /// <remarks>
        /// See the <see cref="ManualBuild"/> event for more details.
        /// </remarks>
        [Category ("Build")]
        public string ManualBuildEvent { get; set; }

        /// <summary>
        /// Gets or sets a script method name that will be used to handle the
        /// <see cref="ModifyResult"/> event.
        /// </summary>
        /// <remarks>
        /// See the <see cref="ModifyResult"/> event for more details.
        /// </remarks>
        [Category ("Build")]
        public string ModifyResultEvent { get; set; }

        /// <summary>
        /// Gets or sets a script method name that will be used to handle the
        /// <see cref="AfterTotals"/> event.
        /// </summary>
        /// <remarks>
        /// See the <see cref="AfterTotals"/> event for more details.
        /// </remarks>
        [Category ("Build")]
        public string AfterTotalsEvent { get; set; }


        /// <summary>
        /// Gets the object that holds the collection of descriptors used
        /// to build a matrix.
        /// </summary>
        /// <remarks>
        /// See the <see cref="MatrixData"/> class for more details.
        /// </remarks>
        [Browsable (false)]
        public MatrixData Data { get; private set; }

        /// <summary>
        /// Gets or sets array of values that describes the currently printing column.
        /// </summary>
        /// <remarks>
        /// Use this property when report is running. It can be used to highlight matrix elements
        /// depending on values of the currently printing column. To do this:
        /// <list type="bullet">
        ///   <item>
        ///     <description>select the cell that you need to highlight;</description>
        ///   </item>
        ///   <item>
        ///     <description>click the "Highlight" button on the "Text" toolbar;</description>
        ///   </item>
        ///   <item>
        ///     <description>add a new highlight condition. Use the <b>Matrix.ColumnValues</b> to
        ///     refer to the value you need to analyze. Note: these values are arrays of <b>System.Object</b>,
        ///     so you need to cast it to actual type before making any comparisons. Example of highlight
        ///     condition: <c>(int)Matrix1.ColumnValues[0] == 2000</c>.
        ///     </description>
        ///   </item>
        /// </list>
        /// </remarks>
        [Browsable (false)]
        public object[] ColumnValues { get; set; }

        /// <summary>
        /// Gets or sets array of values that describes the currently printing row.
        /// </summary>
        /// <remarks>
        /// Use this property when report is running. It can be used to highlight matrix elements
        /// depending on values of the currently printing row. To do this:
        /// <list type="bullet">
        ///   <item>
        ///     <description>select the cell that you need to highlight;</description>
        ///   </item>
        ///   <item>
        ///     <description>click the "Highlight" button on the "Text" toolbar;</description>
        ///   </item>
        ///   <item>
        ///     <description>add a new highlight condition. Use the <b>Matrix.RowValues</b> to
        ///     refer to the value you need to analyze. Note: these values are arrays of <b>System.Object</b>,
        ///     so you need to cast it to actual type before making any comparisons. Example of highlight
        ///     condition: <c>(string)Matrix1.RowValues[0] == "Andrew Fuller"</c>.
        ///     </description>
        ///   </item>
        /// </list>
        /// </remarks>
        [Browsable (false)]
        public object[] RowValues { get; set; }

        /// <summary>
        /// Gets or sets the index of currently printing column.
        /// </summary>
        /// <remarks>
        /// This property may be used to print even columns with alternate color. To do this:
        /// <list type="bullet">
        ///   <item>
        ///     <description>select the cell that you need to highlight;</description>
        ///   </item>
        ///   <item>
        ///     <description>click the "Highlight" button on the "Text" toolbar;</description>
        ///   </item>
        ///   <item>
        ///     <description>add a new highlight condition that uses the <b>Matrix.ColumnIndex</b>,
        ///     for example: <c>Matrix1.ColumnIndex % 2 == 1</c>.
        ///     </description>
        ///   </item>
        /// </list>
        /// </remarks>
        [Browsable (false)]
        public int ColumnIndex { get; set; }

        /// <summary>
        /// Gets or sets the index of currently printing row.
        /// </summary>
        /// <remarks>
        /// This property may be used to print even rows with alternate color. To do this:
        /// <list type="bullet">
        ///   <item>
        ///     <description>select the cell that you need to highlight;</description>
        ///   </item>
        ///   <item>
        ///     <description>click the "Highlight" button on the "Text" toolbar;</description>
        ///   </item>
        ///   <item>
        ///     <description>add a new highlight condition that uses the <b>Matrix.RowIndex</b>,
        ///     for example: <c>Matrix1.RowIndex % 2 == 1</c>.
        ///     </description>
        ///   </item>
        /// </list>
        /// </remarks>
        [Browsable (false)]
        public int RowIndex { get; set; }

        internal MatrixStyleSheet StyleSheet { get; }

        private MatrixHelper Helper { get; }

        internal bool IsResultMatrix => !IsDesigning && Data.Columns.Count == 0 && Data.Rows.Count == 0;

        private BandBase ParentBand
        {
            get
            {
                var parentBand = Band;
                if (parentBand is ChildBand band)
                {
                    parentBand = band.GetTopParentBand;
                }

                return parentBand;
            }
        }

        private DataBand FootersDataBand
        {
            get
            {
                DataBand dataBand = null;
                if (ParentBand is GroupFooterBand)
                {
                    dataBand = ((ParentBand as GroupFooterBand).Parent as GroupHeaderBand).GroupDataBand;
                }
                else if (ParentBand is DataFooterBand)
                {
                    dataBand = ParentBand.Parent as DataBand;
                }

                return dataBand;
            }
        }

        private bool IsOnFooter
        {
            get
            {
                var dataBand = FootersDataBand;
                if (dataBand != null)
                {
                    return DataSource == dataBand.DataSource;
                }

                return false;
            }
        }

        #endregion

        #region Private Methods

        private void CreateResultTable()
        {
            SetResultTable (new TableResult());

            // assign properties from this object. Do not use Assign method: TableResult is incompatible with MatrixObject.
            ResultTable.OriginalComponent = OriginalComponent;
            ResultTable.Alias = Alias;
            ResultTable.Border = Border.Clone();
            ResultTable.Fill = Fill.Clone();
            ResultTable.Bounds = Bounds;
            ResultTable.PrintOnParent = PrintOnParent;
            ResultTable.RepeatHeaders = RepeatHeaders;
            ResultTable.RepeatRowHeaders = RepeatRowHeaders;
            ResultTable.RepeatColumnHeaders = RepeatColumnHeaders;
            ResultTable.Layout = Layout;
            ResultTable.WrappedGap = WrappedGap;
            ResultTable.AdjustSpannedCellsWidth = AdjustSpannedCellsWidth;
            ResultTable.SetReport (Report);
            ResultTable.AfterData += new EventHandler (ResultTable_AfterData);
        }

        private void DisposeResultTable()
        {
            ResultTable.Dispose();
            SetResultTable (null);
        }

        private void ResultTable_AfterData (object sender, EventArgs e)
        {
            OnModifyResult (e);
        }

        private void DataSource_Disposed (object sender, EventArgs e)
        {
            dataSource = null;
        }

        private void WireEvents (bool wire)
        {
            if (IsOnFooter)
            {
                var dataBand = FootersDataBand;
                if (wire)
                {
                    dataBand.BeforePrint += new EventHandler (dataBand_BeforePrint);
                }
                else
                {
                    dataBand.BeforePrint -= new EventHandler (dataBand_BeforePrint);
                }
            }
        }

        private void dataBand_BeforePrint (object sender, EventArgs e)
        {
            var firstRow = (sender as DataBand).IsFirstRow;
            if (firstRow)
            {
                Helper.StartPrint();
            }

            object match = true;
            if (!string.IsNullOrEmpty (Filter))
            {
                match = Report.Calc (Filter);
            }

            if (match is bool and true)
            {
                Helper.AddDataRow();
            }
        }

        #endregion

        #region Protected Methods

        /// <inheritdoc/>
        protected override void DeserializeSubItems (ReportReader reader)
        {
            if (string.Compare (reader.ItemName, "MatrixColumns", true) == 0)
            {
                reader.Read (Data.Columns);
            }
            else if (string.Compare (reader.ItemName, "MatrixRows", true) == 0)
            {
                reader.Read (Data.Rows);
            }
            else if (string.Compare (reader.ItemName, "MatrixCells", true) == 0)
            {
                reader.Read (Data.Cells);
            }
            else
            {
                base.DeserializeSubItems (reader);
            }
        }

        #endregion

        #region Public Methods

        /// <inheritdoc/>
        public override void Assign (Base source)
        {
            base.Assign (source);

            var src = source as MatrixObject;
            AutoSize = src.AutoSize;
            CellsSideBySide = src.CellsSideBySide;
            KeepCellsSideBySide = src.KeepCellsSideBySide;
            DataSource = src.DataSource;
            Filter = src.Filter;
            ShowTitle = src.ShowTitle;
            Style = src.Style;
            MatrixEvenStylePriority = src.MatrixEvenStylePriority;
            SplitRows = src.SplitRows;
            PrintIfEmpty = src.PrintIfEmpty;
            Data = src.Data;
        }

        /// <inheritdoc/>
        public override void Serialize (ReportWriter writer)
        {
            if (writer.SerializeTo != SerializeTo.SourcePages)
            {
                writer.Write (Data.Columns);
                writer.Write (Data.Rows);
                writer.Write (Data.Cells);
            }
            else
            {
                RefreshTemplate (true);
            }

            base.Serialize (writer);
            var c = writer.DiffObject as MatrixObject;

            if (AutoSize != c.AutoSize)
            {
                writer.WriteBool ("AutoSize", AutoSize);
            }

            if (CellsSideBySide != c.CellsSideBySide)
            {
                writer.WriteBool ("CellsSideBySide", CellsSideBySide);
            }

            if (KeepCellsSideBySide != c.KeepCellsSideBySide)
            {
                writer.WriteBool ("KeepCellsSideBySide", KeepCellsSideBySide);
            }

            if (DataSource != c.DataSource)
            {
                writer.WriteRef ("DataSource", DataSource);
            }

            if (Filter != c.Filter)
            {
                writer.WriteStr ("Filter", Filter);
            }

            if (ShowTitle != c.ShowTitle)
            {
                writer.WriteBool ("ShowTitle", ShowTitle);
            }

            if (Style != c.Style)
            {
                writer.WriteStr ("Style", Style);
            }

            if (MatrixEvenStylePriority != c.MatrixEvenStylePriority)
            {
                writer.WriteValue ("MatrixEvenStylePriority", MatrixEvenStylePriority);
            }

            if (SplitRows != c.SplitRows)
            {
                writer.WriteBool ("SplitRows", SplitRows);
            }

            if (PrintIfEmpty != c.PrintIfEmpty)
            {
                writer.WriteBool ("PrintIfEmpty", PrintIfEmpty);
            }

            if (ManualBuildEvent != c.ManualBuildEvent)
            {
                writer.WriteStr ("ManualBuildEvent", ManualBuildEvent);
            }

            if (ModifyResultEvent != c.ModifyResultEvent)
            {
                writer.WriteStr ("ModifyResultEvent", ModifyResultEvent);
            }

            if (AfterTotalsEvent != c.AfterTotalsEvent)
            {
                writer.WriteStr ("AfterTotalsEvent", AfterTotalsEvent);
            }
        }

        /// <summary>
        /// Creates or updates the matrix template.
        /// </summary>
        /// <remarks>
        /// Call this method after you modify the matrix descriptors using the <see cref="Data"/>
        /// object's properties.
        /// </remarks>
        public void BuildTemplate()
        {
            Helper.BuildTemplate();
        }

        #endregion

        #region Report Engine

        /// <inheritdoc/>
        public override void InitializeComponent()
        {
            base.InitializeComponent();
            WireEvents (true);
        }

        /// <inheritdoc/>
        public override void FinalizeComponent()
        {
            base.FinalizeComponent();
            WireEvents (false);
        }

        /// <inheritdoc/>
        public override string[] GetExpressions()
        {
            List<string> expressions = new List<string>();
            expressions.AddRange (base.GetExpressions());

            Helper.UpdateDescriptors();
            List<MatrixDescriptor> descrList = new List<MatrixDescriptor>();
            descrList.AddRange (Data.Columns.ToArray());
            descrList.AddRange (Data.Rows.ToArray());
            descrList.AddRange (Data.Cells.ToArray());

            foreach (var descr in descrList)
            {
                expressions.Add (descr.Expression);
                if (descr.TemplateCell != null)
                {
                    descr.TemplateCell.AllowExpressions = false;
                }
            }

            if (!string.IsNullOrEmpty (Filter))
            {
                expressions.Add (Filter);
            }

            return expressions.ToArray();
        }

        /// <inheritdoc/>
        public override void SaveState()
        {
            saveVisible = Visible;
            var parent = Parent as BandBase;
            if (!Visible || parent is { Visible: false })
            {
                return;
            }

            // create the result table that will be rendered in the preview
            CreateResultTable();
            Visible = false;

            if (parent != null && !PrintOnParent)
            {
                parent.Height = Top;
                parent.CanGrow = false;
                parent.CanShrink = false;
                parent.AfterPrint += ResultTable.GeneratePages;
            }
        }

        /// <inheritdoc/>
        public override void GetData()
        {
            base.GetData();

            if (!IsOnFooter)
            {
                Helper.StartPrint();
                Helper.AddDataRows();
            }
        }

        /// <inheritdoc/>
        public override void OnAfterData (EventArgs e)
        {
            base.OnAfterData (e);
            Helper.FinishPrint();

            if (PrintOnParent)
            {
                ResultTable.AddToParent (Parent);
            }
        }

        /// <inheritdoc/>
        public override void RestoreState()
        {
            var parent = Parent as BandBase;
            if (!saveVisible || parent is { Visible: false })
            {
                return;
            }

            if (parent != null && !PrintOnParent)
            {
                parent.AfterPrint -= ResultTable.GeneratePages;
            }

            DisposeResultTable();
            Visible = saveVisible;
        }

        /// <summary>
        /// This method fires the <b>ManualBuild</b> event and the script code connected to the <b>ManualBuildEvent</b>.
        /// </summary>
        /// <param name="e">Event data.</param>
        public void OnManualBuild (EventArgs e)
        {
            if (ManualBuild != null)
            {
                ManualBuild (this, e);
            }

            InvokeEvent (ManualBuildEvent, e);
        }

        /// <summary>
        /// This method fires the <b>ModifyResult</b> event and the script code connected to the <b>ModifyResultEvent</b>.
        /// </summary>
        /// <param name="e">Event data.</param>
        public void OnModifyResult (EventArgs e)
        {
            if (ModifyResult != null)
            {
                ModifyResult (this, e);
            }

            InvokeEvent (ModifyResultEvent, e);
        }

        /// <summary>
        /// This method fires the <b>AfterTotals</b> event and the script code connected to the <b>AfterTotalsEvent</b>.
        /// </summary>
        /// <param name="e">Event data.</param>
        public void OnAfterTotals (EventArgs e)
        {
            if (AfterTotals != null)
            {
                AfterTotals (this, e);
            }

            InvokeEvent (AfterTotalsEvent, e);
        }

        /// <summary>
        /// Adds a value in the matrix.
        /// </summary>
        /// <param name="columnValues">Array of column values.</param>
        /// <param name="rowValues">Array of row values.</param>
        /// <param name="cellValues">Array of data values.</param>
        /// <remarks>
        /// This is a shortcut method to call the matrix <b>Data.AddValue</b>.
        /// See the <see cref="MatrixData.AddValue(object[],object[],object[])"/> method for more details.
        /// </remarks>
        public void AddValue (object[] columnValues, object[] rowValues, object[] cellValues)
        {
            Data.AddValue (columnValues, rowValues, cellValues, 0);
        }

        /// <summary>
        /// Gets the value of the data cell with the specified index.
        /// </summary>
        /// <param name="index">Zero-based index of the data cell.</param>
        /// <returns>The cell's value.</returns>
        /// <remarks>
        /// Use this method in the cell's expression if the cell has custom totals
        /// (the total function is set to "Custom"). The example:
        /// <para/>Matrix1.Value(0) / Matrix1.Value(1)
        /// <para/>will return the result of dividing the first data cell's value by the second one.
        /// </remarks>
        public Variant Value (int index)
        {
            var value = Helper.CellValues[index];
            if (value == null)
            {
                value = 0;
            }

            return new Variant (value);
        }

        #endregion

        /// <summary>
        /// Initializes a new instance of the <see cref="MatrixObject"/> class.
        /// </summary>
        public MatrixObject()
        {
            autoSize = true;
            Data = new MatrixData();
            ManualBuildEvent = "";
            AfterTotalsEvent = "";
            Helper = new MatrixHelper (this);
            InitDesign();
            StyleSheet = new MatrixStyleSheet();
            StyleSheet.Load (ResourceLoader.GetStream ("cross.frss"));
            style = "";
            Filter = "";
            SplitRows = false;
            PrintIfEmpty = true;
        }
    }
}
